const defaultSkinConfig = {
  backgroundStyle: null, // 画布背景样式
  lineStyle: '#ff9900', // 连接线样式
  pelStyle: '#0000ff', // 图元线条样式

  titleStyle: '#0000ff', // 图元标题样式
  titleFont: null, // 图元标题字体样式
  dataTagStyle: '#008000', // 图元参数样式
  dataTagFont: null, // 图元参数字体样式
  dataStyle: '#000000', // 图元数据样式
  dataFont: null // 图元数据字体样式
}
const defaultConfig = {
  showGrid: 'true',
  showTags: [], // 允许显示的参数
  defaultStateStyle: null, // 默认图元填充样式
  normalStateStyle: '#ff0000', // 正常状态图元填充样式
  abnormalStateStyle: '#00ff00', // 非正常状态图元填充样式
  canvasId: 'canvas',
  context: '2d',
  canvasWidth: 1200,
  canvasHeight: 1000,
  zoomX: 1,
  zoomY: 1,
  startX: 60,
  startY: 60,
  alignX: 'start',
  alignY: 'top',
  gridStyle: 'gray',
  gridSpace: 20,
  gridLineWidth: 1,
  lineWidth: 2, // 连接线宽度
  pelLineWidth: 2, // 图元线条宽度
  pelSpaceX: 60, // 图元的横向间隔,建议horizontal_space:pel_length
  pelSpaceY: 100, // 图元的纵向间隔,建议vertical_space:pel_length
  pelWidth: 10, // 图元的标准宽度,横向图元则为标准高度,建议spec_height/pel_width:8
  pelLength: 80, // 图元的标准长度,横向图元则为标准宽度,建议spec_height/pel_width:8
  textSpace: 10, // 图元中心与文字左侧的间隔
  textWidth: 100, // 文本的最大宽度
  textHeight: 15, // 文本的最大高度
  // 默认皮肤样式
  ...defaultSkinConfig
}

// 存放渲染一次系统图的详细数据
// width,Height,图元的宽和高
// (x,y),图元起点位置
// end(x,y),图元终点位置
// connect_pre(x,y),连接前一图元的位置,默认为起点(x,y)
// connect_next(x,y),,连接后一图元的位置,默认为终点end(x,y)
// title(x,y),图元标题起点位置
// data(x,y),图元数据起点位置
// data(width,height),图元数据区域宽度与高度
export default class MonitorChart {
  canvas = null;
  context = null;
  config = {};
  chartJson = {};
  idMapUid = {};
  // 构造函数,new MonitorCHart()时默认执行
  constructor(canvasId, configJson = {}) {
    let originPoint = { x: 0, y: 0 }
    this.convertConfig(configJson)
    this.config = { ...defaultConfig, ...configJson }
    this.config.canvasId = canvasId || this.config.canvasId
    this.canvas = document.getElementById(this.config.canvasId)
    this.context = this.canvas.getContext(this.config.context)
    this.canvas.draggable = true
    this.init()
    const _this = this
    this.canvas.addEventListener('dragend', function(event) {
    })
    this.canvas.addEventListener('dragover', function(event) {
      event.preventDefault()
      event.stopPropagation()
    })
    this.canvas.addEventListener('dragenter', function(event) {
      event.preventDefault()
      event.stopPropagation()
    })
    this.canvas.addEventListener('drag', function(event) {
      const newPosition = { x: event.clientX, y: event.clientY }
      _this.config.startX += (newPosition.x - originPoint.x)
      _this.config.startY += (newPosition.y - originPoint.y)
      _this.draw()
      originPoint = newPosition
    })
    this.canvas.addEventListener('mousewheel', function(event) {
      event.preventDefault()
      event.stopPropagation()
      _this.zoom(1 + event.deltaY / 1000, 1 + event.deltaY / 1000)
    })
    this.canvas.addEventListener('mousedown', function(event) {
      originPoint.x = event.clientX
      originPoint.y = event.clientY
    })
  }
  // 转换对象
  convertConfig(configJson) {
    for (const key in configJson) {
      try {
        // Do not convert value of: undefined, null, false, true, number
        configJson[key] = configJson[key].trim()
        if (!configJson[key]) {
          configJson[key] = null
        } else if (isNaN(configJson[key])) {
          if (configJson[key].toUpperCase() === 'TRUE') {
            configJson[key] = true
          } else if (configJson[key].toUpperCase() === 'FALSE') {
            configJson[key] = false
          }
        } else {
          configJson[key] = parseInt(configJson[key])
        }
      } catch (ex) {
        continue
      }
    }
  }
  // 初始化环境
  init() {
    this.canvas.width = this.config.canvasWidth
    this.canvas.height = this.config.canvasHeight
    this.context.textAlign = this.config.alignX
    this.context.textBaseline = this.config.alignY
    this.context.clearRect(0, 0, this.config.canvasWidth, this.config.canvasHeight)
    if (this.config.backgroundStyle) {
      this.context.fillStyle = this.config.backgroundStyle
      this.context.fillRect(0, 0, this.config.canvasWidth, this.config.canvasHeight)
    }
    this.context.scale(this.config.zoomX, this.config.zoomY)
    if (this.config.showGrid) {
      this.context.lineWidth = this.config.gridLineWidth
      this.context.strokeStyle = this.config.gridStyle
      this.drawGrid(0, 0, this.config.canvasWidth / this.config.zoomX, this.config.canvasHeight / this.config.zoomY, this.config.gridSpace)
    }
    this.context.translate(this.config.startX, this.config.startY)
  }

  // 获取点击的图元信息
  getPel(event) {
    const eventX = (event.clientX - this.canvas.getBoundingClientRect().left) / this.config.zoomX - this.config.startX
    const eventY = (event.clientY - this.canvas.getBoundingClientRect().top) / this.config.zoomY - this.config.startY
    for (const key in this.chartJson) {
      if (this.chartJson[key].focus_x) {
        if (eventX > this.chartJson[key].focus_x && eventX < this.chartJson[key].focus_end_x && eventY > this.chartJson[key].focus_y && eventY < this.chartJson[key].focus_end_y) {
          return this.chartJson[key]
        }
      }
    }
    return null
  }

  // 缩放功能
  zoom(x, y) {
    if (x > 0 && y > 0) {
      this.config.zoomX *= x
      this.config.zoomY *= y
      this.draw()
    }
  }

  // 根据图元数据渲染图元
  draw(pelJson = this.chartJson) {
    this.init()
    this.drawChart(pelJson)
  }

  // 加载数据并绘图
  load(layoutJson) {
    // try {
    if (layoutJson && typeof (layoutJson) === 'object') {
      this.chartJson = this.getChartJson(layoutJson)
      this.draw()
    } else {
      return false
    }
    // } catch (e) {
    //   return false;
    // }
    return true
  }

  // 根据图元数据渲染图元
  drawChart(chartJson = this.chartJson, ctx = this.context, config = this.config) {
    for (const key in chartJson) {
      // 画连接线
      ctx.lineWidth = config.lineWidth
      ctx.strokeStyle = config.lineStyle
      if (chartJson[chartJson[key].parent_uid]) {
        ctx.beginPath()
        ctx.moveTo(chartJson[key].connect_previous_x, chartJson[key].connect_previous_y)
        ctx.lineTo(chartJson[chartJson[key].parent_uid].connect_next_x, chartJson[chartJson[key].parent_uid].connect_next_y)
        ctx.stroke()
        if (chartJson[chartJson[key].previous_uid]) {
          const middleX = Math.min(config.pelLength + config.pelSpaceX / 3, (chartJson[chartJson[key].parent_uid].connect_previous_x - chartJson[chartJson[key].previous_uid].connect_next_x) * 4 / 5)
          ctx.beginPath()
          ctx.moveTo(chartJson[chartJson[key].parent_uid].connect_previous_x, chartJson[chartJson[key].parent_uid].connect_previous_y)
          ctx.lineTo(chartJson[chartJson[key].previous_uid].connect_next_x + middleX, chartJson[chartJson[key].parent_uid].connect_previous_y)
          ctx.lineTo(chartJson[chartJson[key].previous_uid].connect_next_x + middleX, chartJson[chartJson[key].previous_uid].connect_next_y)
          ctx.lineTo(chartJson[chartJson[key].previous_uid].connect_next_x, chartJson[chartJson[key].previous_uid].connect_next_y)
          ctx.stroke()
        }
      }
      // 渲染图元
      ctx.lineWidth = config.pelLineWidth
      ctx.strokeStyle = config.pelStyle
      let style = null
      if (!chartJson[key].status || chartJson[key].status === null) {
        style = config.defaultStateStyle
      } else if (chartJson[key].status === '1') {
        style = config.normalStateStyle
      } else {
        style = config.abnormalStateStyle
      }
      this.drawPel(chartJson[key], style)
      // 填充标题
      ctx.fillStyle = config.titleStyle
      ctx.font = config.titleFont
      // TODO,must delete '+ chartJson[key].pointType' before publish
      ctx.fillText(chartJson[key].name, chartJson[key].title_x, chartJson[key].title_y)
      // 填充数据
      if (chartJson[key].data) {
        this.fillData(chartJson[key].data_x, chartJson[key].data_y, chartJson[key].data, config.textHeight, chartJson[key].data_width, chartJson[key].data_height)
      }
    }
  }

  // 填充数据
  // 起点坐标(x,y),数据内容data
  // 宽度width默认150;高度length,默认width/3
  fillData(x = 0, y = 0, data = null, textHeight = this.config.textHeight, width = textHeight * 9, height = textHeight * 3, ctx = this.context, config = this.config) {
    if (data) {
      let lengthLeft = 0
      for (const key in data) {
        lengthLeft = Math.max(lengthLeft, ctx.measureText(key + ': ').width)
      }
      const lengthRight = width / 2 - lengthLeft
      let top = y
      let left = x + lengthLeft
      for (const key in data) {
        ctx.fillStyle = config.tagStyle
        ctx.font = config.tagFont
        ctx.textAlign = 'right'
        ctx.fillText(key + ': ', left, top, lengthLeft)
        ctx.fillStyle = config.dataStyle
        ctx.font = config.dataFont
        ctx.textAlign = 'left'
        ctx.fillText(data[key], left, top, lengthRight)
        top += textHeight
        if (height - textHeight < top - y) {
          top = y
          left += (lengthLeft + lengthRight)
          if (x + width <= left) {
            left = x + lengthLeft
          }
        }
      }
    }
  }

  // 渲染状态
  // 起点坐标(x,y),宽度width,默认10;高度height,默认3倍width
  fillPel(style = null, x = 0, y = 0, width = 10, height = width * 3, ctx = this.context) {
    ctx.fillStyle = style
    ctx.fillRect(x, y, width, height)
  }

  // 馈线柜
  // 起点坐标(x,y),宽度width,0;长度length,默认8倍width
  drawFeeder(x = 0, y = 0, width = 0, length = 80, style = null, ctx = this.context) {
    ctx.beginPath()
    // 纵向
    if (width > length) {
      width = width + length
      length = width - length
      width = width - length
    }
    if (style) {
      this.fillPel(style, x, y, length, width)
    }
    ctx.strokeRect(x, y, length, width)
  }

  // 熔断器
  // 起点坐标(x,y),宽度width,默认10;长度length,默认8倍width
  drawFuse(x = 0, y = 0, width = 10, length = width * 8, style = null, ctx = this.context) {
    width = width || 10
    length = length || width * 8
    ctx.beginPath()
    // 纵向
    if (width <= length) {
      length = length - width * 5
      if (style) {
        this.fillPel(style, x - width / 2, y + width * 5 / 2, width, length)
      }
      ctx.strokeRect(x - width / 2, y + width * 5 / 2, width, length)
      ctx.moveTo(x, y)
      ctx.lineTo(x, y + width * 5 + length)
    } else { // 横向
      width = width + length
      length = width - length
      width = width - length
      length = length - width * 5
      if (style) {
        this.fillPel(style, x + width * 5 / 2, y - width / 2, length, width)
      }
      ctx.strokeRect(x + width * 5 / 2, y - width / 2, length, width)
      ctx.moveTo(x, y)
      ctx.lineTo(x + width * 5 + length, y)
    }
    ctx.stroke()
  }

  // 开关
  // 起点坐标(x,y),宽度width,默认10;长度length,默认8倍width
  drawSwitch(x = 0, y = 0, width = 10, length = width * 10, style = null, ctx = this.context) {
    width = width || 10
    length = length || width * 8
    ctx.beginPath()
    // 纵向
    if (width <= length) {
      length = length - width * 5
      if (style) {
        this.fillPel(style, x - width / 2, y + width * 5 / 2, width, length)
      }
      ctx.strokeRect(x - width / 2, y + width * 5 / 2, width, length)
      ctx.moveTo(x, y)
      ctx.lineTo(x, y + width * 5 / 2)
      ctx.moveTo(x, y + width * 5 / 2 + length)
      ctx.lineTo(x, y + width * 5 + length)
    } else { // 横向
      width = width + length
      length = width - length
      width = width - length
      length = length - width * 5
      if (style) {
        this.fillPel(style, x + width * 5 / 2, y - width / 2, length, width)
      }
      ctx.strokeRect(x + width * 5 / 2, y - width / 2, length, width)
      ctx.moveTo(x, y)
      ctx.lineTo(x + width * 5 / 2, y)
      ctx.moveTo(x + width * 5 / 2 + length, y)
      ctx.lineTo(x + width * 5 + length, y)
    }
    ctx.stroke()
  }

  // 断路器手车
  // 起点坐标(x,y),宽度width,默认10;长度length,默认8倍width
  drawBreaker(x = 0, y = 0, width = 10, length = width * 8, style = null, ctx = this.context) {
    ctx.beginPath()
    if (width <= length) {
      length = length - width * 5
      if (style) {
        this.fillPel(style, x - width / 2, y + width * 5 / 2, width, length)
      }
      ctx.strokeRect(x - width / 2, y + width * 5 / 2, width, length)
      ctx.moveTo(x, y)
      ctx.lineTo(x, y + width * 5 / 2)
      ctx.moveTo(x, y + width * 5 / 2 + length)
      ctx.lineTo(x, y + width * 5 + length)

      y += width
      ctx.moveTo(x - width / 2, y + width / 2)
      ctx.lineTo(x, y)
      ctx.lineTo(x + width / 2, y + width / 2)

      y += width / 2
      ctx.moveTo(x - width / 2, y + width / 2)
      ctx.lineTo(x, y)
      ctx.lineTo(x + width / 2, y + width / 2)

      y += (width * 2 + length)
      ctx.moveTo(x - width / 2, y - width / 2)
      ctx.lineTo(x, y)
      ctx.lineTo(x + width / 2, y - width / 2)

      y += width / 2
      ctx.moveTo(x - width / 2, y - width / 2)
      ctx.lineTo(x, y)
      ctx.lineTo(x + width / 2, y - width / 2)
    } else {
      width = width + length
      length = width - length
      width = width - length
      length = length - width * 5
      if (style) {
        this.fillPel(style, x + width * 5 / 2, y - width / 2, length, width)
      }
      ctx.strokeRect(x + width * 5 / 2, y - width / 2, length, width)
      ctx.moveTo(x, y)
      ctx.lineTo(x + width * 5 / 2, y)
      ctx.moveTo(x + width * 5 / 2 + length, y)
      ctx.lineTo(x + width * 5 + length, y)

      x += width
      ctx.moveTo(x + width / 2, y - width / 2)
      ctx.lineTo(x, y)
      ctx.lineTo(x + width / 2, y + width / 2)

      x += width / 2
      ctx.moveTo(x + width / 2, y - width / 2)
      ctx.lineTo(x, y)
      ctx.lineTo(x + width / 2, y + width / 2)

      x += (width * 2 + length)
      ctx.moveTo(x - width / 2, y - width / 2)
      ctx.lineTo(x, y)
      ctx.lineTo(x - width / 2, y + width / 2)

      x += width / 2
      ctx.moveTo(x - width / 2, y - width / 2)
      ctx.lineTo(x, y)
      ctx.lineTo(x - width / 2, y + width / 2)
    }
    ctx.stroke()
  }

  // 负载开关
  // 起点坐标(x,y),宽度width,默认10;长度length,默认8倍width
  drawloadSwitch(x = 0, y = 0, width = 10, length = width * 8, style = null, ctx = this.context) {
    ctx.beginPath()
    if (width <= length) {
      length = length - width * 5
      if (style) {
        this.fillPel(style, x - width / 2, y + width * 5 / 2, width, length)
      }
      ctx.strokeRect(x - width / 2, y + width * 5 / 2, width, length)
      ctx.moveTo(x, y)
      ctx.lineTo(x, y + width * 5 / 2)
      ctx.moveTo(x, y + width * 5 / 2 + length)
      ctx.lineTo(x, y + width * (5 - Math.cos(Math.PI / 6)) + length)
      y = y + length + width * (5 - Math.cos(Math.PI / 6))
      ctx.moveTo(x - width / 2, y)
      ctx.lineTo(x, y + width * Math.cos(Math.PI / 6))
      ctx.lineTo(x + width / 2, y)
    } else {
      width = width + length
      length = width - length
      width = width - length
      length = length - width * 5
      if (style) {
        this.fillPel(style, x + width * 5 / 2, y - width / 2, length, width)
      }
      ctx.strokeRect(x + width * 5 / 2, y - width / 2, length, width)
      ctx.moveTo(x, y)
      ctx.lineTo(x + width * 5 / 2, y)
      ctx.moveTo(x + width * 5 / 2 + length, y)
      ctx.lineTo(x + width * (5 - Math.cos(Math.PI / 6)) + length, y)
      x = x + length + width * (5 - Math.cos(Math.PI / 6))
      ctx.moveTo(x, y - width / 2)
      ctx.lineTo(x + width * Math.cos(Math.PI / 6), y)
      ctx.lineTo(x, y + width / 2)
    }
    ctx.closePath()
    ctx.fillStyle = ctx.strokeStyle
    ctx.fill()
    ctx.stroke()
  }

  // 避雷器
  // 起点坐标(x,y),宽度width,默认10;高度height,默认8倍width
  drawArrester(x = 0, y = 0, width = 10, height = width * 8, style = null, ctx = this.context) {
    ctx.beginPath()
    if (width <= height) {
      // 纵向图元
      height = height - width * 5
      if (style) {
        this.fillPel(style, x - width / 2, y + width * 5 / 2, width, height)
      }
      ctx.strokeRect(x - width / 2, y + width * 5 / 2, width, height)
      ctx.moveTo(x, y)
      ctx.lineTo(x, y + width * 3)
      // 画内部三角形
      y += width * 3
      ctx.moveTo(x - width / 4, y)
      ctx.lineTo(x, y + height / 3)
      ctx.lineTo(x + width / 4, y)
      // 移动到接进线位置
      y += (height - width / 2)
    } else {
      // 横向图元
      width = width + height
      height = width - height
      width = width - height
      height = height - width * 5
      if (style) {
        this.fillPel(style, x + width * 5 / 2, y - width / 2, height, width)
      }
      ctx.strokeRect(x + width * 5 / 2, y - width / 2, height, width)
      ctx.moveTo(x, y)
      ctx.lineTo(x + width * 3, y)
      ctx.moveTo(x + width * 5 / 2 + height, y)
      ctx.lineTo(x + width * 5 + height, y)

      x += width * 3
      ctx.moveTo(x, y - width / 4)
      ctx.lineTo(x + height / 3, y)
      ctx.lineTo(x, y + width / 4)

      x += (height + width * 2)
    }
    ctx.closePath()
    ctx.fill()
    ctx.stroke()
    this.drawGroundLine(x, y, width * 2)
  }

  // 中心接地变压器
  // 起点坐标(x,y),最大宽度width,默认50
  drawTransformer(x = 0, y = 0, width = 50, style = null, ctx = this.context) {
    const r = width / 5
    // 画两个圆
    ctx.beginPath()
    ctx.arc(x, y + r * 3, r, 0, Math.PI * 2)
    ctx.moveTo(x + r, y + r * 9 / 2)
    ctx.arc(x, y + r * 9 / 2, r, 0, Math.PI * 2)
    if (style) {
      ctx.fillStyle = style
      ctx.fill()
    }
    ctx.stroke()
    // 画正三角形
    y += r * 3
    ctx.beginPath()
    ctx.moveTo(x - r / 3, y)
    ctx.lineTo(x, y - r * 2 / 3 * Math.cos(Math.PI / 6))
    ctx.lineTo(x + r / 3, y)
    ctx.closePath()
    ctx.stroke()
    // 画线
    y -= r * 3
    ctx.beginPath()
    ctx.moveTo(x, y)
    ctx.lineTo(x, y + r * 2)
    ctx.moveTo(x, y + r * 11 / 2)
    ctx.lineTo(x, y + r * 8)
    // 画Y型图
    y += r * 9 / 2
    ctx.moveTo(x, y)
    ctx.lineTo(x, y + r / 2)
    ctx.moveTo(x, y)
    ctx.lineTo(x - r / 2 * Math.cos(Math.PI / 6), y - r / 2 * Math.sin(Math.PI / 6))
    ctx.moveTo(x, y)
    ctx.lineTo(x + r / 2 * Math.cos(Math.PI / 6), y - r / 2 * Math.sin(Math.PI / 6))
    ctx.moveTo(x, y)
    ctx.lineTo(x + r * 3, y)
    ctx.stroke()
    // 画接地线
    this.drawGroundLine(x + r * 3, y, r * 2)
  }

  // 电容器
  // 起点坐标(x,y),最大宽度width,默认40
  drawCapacitance(x = 0, y = 0, width = 40, ctx = this.context) {
    // r接圆半径,下三角形半径为r*2
    let r = width / 4
    ctx.beginPath()
    ctx.moveTo(x, y)
    ctx.lineTo(x, y + r)
    ctx.arc(x, y + r * 2, r, Math.PI * 3 / 2, 0, true)
    ctx.lineTo(x, y + r * 2)
    ctx.lineTo(x, y + r * 4)
    y += r * 4
    r *= 4
    let tmpX
    let tmpY
    // 斜线一半的长度tmpL对应的(|x|,|y|)
    const tmpL = r * (1 - Math.cos(Math.PI / 6))
    const tmpLx = tmpL * Math.cos(Math.PI / 6)
    const tmpLy = tmpL * Math.sin(Math.PI / 6)
    // 画三角形
    tmpX = x - r * 3 / 8 * Math.sin(Math.PI / 6)
    tmpY = y + r * 3 / 8 * Math.cos(Math.PI / 6)
    ctx.lineTo(tmpX, tmpY)
    ctx.moveTo(tmpX - tmpLx, tmpY - tmpLy)
    ctx.lineTo(tmpX + tmpLx, tmpY + tmpLy)
    tmpX = x - r * 5 / 8 * Math.sin(Math.PI / 6)
    tmpY = y + r * 5 / 8 * Math.cos(Math.PI / 6)
    ctx.moveTo(tmpX - tmpLx, tmpY - tmpLy)
    ctx.lineTo(tmpX + tmpLx, tmpY + tmpLy)
    ctx.moveTo(tmpX, tmpY)
    tmpX = x - r * Math.sin(Math.PI / 6)
    tmpY = y + r * Math.cos(Math.PI / 6)
    ctx.lineTo(tmpX, tmpY)
    tmpX = x - r / 4 * Math.sin(Math.PI / 6)
    ctx.lineTo(tmpX, tmpY)
    ctx.moveTo(tmpX, tmpY - tmpL)
    ctx.lineTo(tmpX, tmpY + tmpL)
    tmpX = x + r / 4 * Math.sin(Math.PI / 6)
    ctx.moveTo(tmpX, tmpY - tmpL)
    ctx.lineTo(tmpX, tmpY + tmpL)
    ctx.moveTo(tmpX, tmpY)
    tmpX = x + r * Math.sin(Math.PI / 6)
    ctx.lineTo(tmpX, tmpY)
    tmpX = x + r * 5 / 8 * Math.sin(Math.PI / 6)
    tmpY = y + r * 5 / 8 * Math.cos(Math.PI / 6)
    ctx.lineTo(tmpX, tmpY)
    ctx.moveTo(tmpX + tmpLx, tmpY - tmpLy)
    ctx.lineTo(tmpX - tmpLx, tmpY + tmpLy)
    tmpX = x + r * 3 / 8 * Math.sin(Math.PI / 6)
    tmpY = y + r * 3 / 8 * Math.cos(Math.PI / 6)
    ctx.moveTo(tmpX + tmpLx, tmpY - tmpLy)
    ctx.lineTo(tmpX - tmpLx, tmpY + tmpLy)
    ctx.moveTo(tmpX, tmpY)
    ctx.lineTo(x, y)
    ctx.stroke()
  }

  // 互感器
  // 起点坐标(x,y),最大宽度width,默认40
  drawInductor(x = 0, y = 0, width = 40, style = null, ctx = this.context) {
    const r = width / 8
    ctx.beginPath()
    ctx.arc(x, y + r * 4, r, 0, Math.PI * 2)
    ctx.moveTo(x - r * 2, y + r * 4)
    ctx.arc(x - r * 3, y + r * 4, r, 0, Math.PI * 2)
    ctx.moveTo(x + r * 4, y + r * 3)
    ctx.arc(x + r * 3, y + r * 4, r, 0, Math.PI * 2)
    if (style) {
      ctx.fillStyle = style
      ctx.fill()
    }
    ctx.stroke()

    ctx.beginPath()
    ctx.moveTo(x, y)
    ctx.lineTo(x, y + r * 8)
    ctx.moveTo(x - r * 3, y + r)
    ctx.lineTo(x - r * 3, y + r * 7)
    ctx.moveTo(x + r * 3, y + r)
    ctx.lineTo(x + r * 3, y + r * 7)
    ctx.stroke()
  }

  // 接地线
  // 起点坐标(x,y),最大宽度width,默认20
  drawGroundLine(x = 0, y = 0, width = 20, ctx = this.context) {
    ctx.beginPath()
    ctx.moveTo(x, y)
    y += width * 3 / 4
    ctx.lineTo(x, y)
    ctx.moveTo(x - width / 2, y)
    ctx.lineTo(x + width / 2, y)
    ctx.moveTo(x - width / 4, y + width / 4)
    ctx.lineTo(x + width / 4, y + width / 4)
    ctx.moveTo(x - width / 8, y + width / 2)
    ctx.lineTo(x + width / 8, y + width / 2)
    ctx.stroke()
  }

  // 画网格线
  drawGrid(x = 0, y = 0, width = 800, height = 600, space = 10, ctx = this.context) {
    ctx.beginPath()
    for (let dx = x; dx < x + width; dx += space) {
      ctx.moveTo(dx, y)
      ctx.lineTo(dx, y + height)
    }
    for (let dy = y; dy < y + height; dy += space) {
      ctx.moveTo(x, dy)
      ctx.lineTo(x + width, dy)
    }
    ctx.stroke()
  }

  // 更新数据,dataJson格式如下
  // {
  //  point_id:"",
  //  status:{
  //    flag:""
  //  },
  //  data:{
  //    tagId:"tagValue"
  //  }
  // }
  update(dataJson, chartJson = this.chartJson) {
    var pelId = null
    if (dataJson && dataJson.point) {
      pelId = dataJson.point
      if (chartJson[this.idMapUid[pelId]]) {
        if (dataJson.status && dataJson.status.sw != null) {
          chartJson[this.idMapUid[pelId]].status = dataJson.status.sw
        }
        if (dataJson.data) {
          chartJson[this.idMapUid[pelId]].data = Object.assign({}, chartJson[this.idMapUid[pelId]].data, this.flitrateData(dataJson.data))
        }
        this.draw(chartJson)
      }
    }
  }

  // 仅更新状态
  updateStatus(pelId, flag) {
    if (pelId != null && flag != null) {
      this.chartJson[pelId].status = flag
      var style = null
      if (flag == null) {
        style = this.config.defaultStateStyle
      } else if (flag) {
        style = this.config.normalStateStyle
      } else {
        style = this.config.abnormalStateStyle
      }
      this.drawPel(this.chartJson[pelId], style)
    }
  }

  // 仅更新数据,dataJson格式如下
  // {
  //  tagId:"tagValue"
  // }
  updateData(pelId, dataJson, ctx = this.context) {
    if (pelId != null && dataJson != null) {
      var datajson = this.flitrateData(dataJson)
      this.chartJson[pelId].data = datajson
      ctx.clearRect(this.chartJson[pelId].data_x, this.chartJson[pelId].data_y, this.chartJson[pelId].data_width, this.chartJson[pelId].data_height)
      if (this.config.backgroundStyle) {
        ctx.fillStyle = this.config.background_style
        ctx.fillRect(this.chartJson[pelId].data_x, this.chartJson[pelId].data_y, this.chartJson[pelId].data_width, this.chartJson[pelId].data_height)
      }
      this.fillData(this.chartJson[pelId].data_x, this.chartJson[pelId].data_y, datajson, this.config.textHeight, this.chartJson[pelId].data_width, this.chartJson[pelId].data_height)
    }
  }

  // 根据show_tag显示数组过滤数据
  // TODO reduce 重构
  flitrateData(dataJson, config = this.config) {
    var datajson = {}
    if (config.showTags.length > 0) {
      // console.log(config.show_tag[0]);
      for (var i = 0; i < config.showTags.length; ++i) {
        if (dataJson[config.showTags[i]] != null) {
          datajson[config.showTags[i]] = (Math.ceil(dataJson[config.showTags[i]] * 100)) / 100
        }
      }
    } else {
      datajson = dataJson
    }
    return datajson
  }

  // 根据图元类别画图元
  drawPel(pelJson, style) {
    switch (pelJson.pointType) {
      case 0:
        this.drawFeeder(pelJson.x, pelJson.y, pelJson.width, pelJson.length, style)
        break
      case 4:
        this.drawTransformer(pelJson.x, pelJson.y, pelJson.width, style)
        break
      case 6:
        this.drawInductor(pelJson.x, pelJson.y, pelJson.width * 4, style)
        break
      case 8:
        this.drawCapacitance(pelJson.x, pelJson.y, pelJson.width * 4)
        break
      case 1:
      case 2:
        this.drawFeeder(pelJson.x, pelJson.y, pelJson.width, pelJson.length, style)
        break
      case 9:
        this.drawArrester(pelJson.x, pelJson.y, pelJson.width, pelJson.length, style)
        break
      case 10:
        this.drawArrester(pelJson.x, pelJson.y, pelJson.length, pelJson.width, style)
        break
      case 11:
        this.drawFuse(pelJson.x, pelJson.y, pelJson.width, pelJson.ength, style)
        break
      case 12:
        this.drawFuse(pelJson.x, pelJson.y, pelJson.ength, pelJson.width, style)
        break
      case 13:
        this.drawBreaker(pelJson.x, pelJson.y, pelJson.width, pelJson.length, style)
        break
      case 14:
        this.drawBreaker(pelJson.x, pelJson.y, pelJson.length, pelJson.width, style)
        break
      case 16:
        this.drawSwitch(pelJson.x, pelJson.y, pelJson.width, pelJson.length, style)
        break
      case 15:
        this.drawSwitch(pelJson.x, pelJson.y, pelJson.length, pelJson.width, style)
        break
      case 17:
        this.drawloadSwitch(pelJson.x, pelJson.y, pelJson.width, pelJson.length, style)
        break
      case 18:
        this.drawloadSwitch(pelJson.x, pelJson.y, pelJson.length, pelJson.width, style)
        break
      default:
        this.drawFeeder(pelJson.x, pelJson.y, pelJson.width, pelJson.length, style)
        break
    }
  }

  // / 解析布局数据为图元数据
  getChartJson(layoutData, config = this.config) {
    const resultData = {}
    let positionX = 0
    let positionY = 0
    let pointPositionX = 0
    let pointPositionY = 0
    for (const heightVoltageBus of layoutData.children || []) {
      this.idMapUid[heightVoltageBus.id] = heightVoltageBus.uid
      // 高压总进线图形,一条横向直线
      const pel = {}
      pel.pointType = heightVoltageBus.info && heightVoltageBus.info.pointType ? parseInt(heightVoltageBus.info.pointType) : 0
      pel.x = positionX
      pel.y = positionY
      pel.width = 0 // 80
      pel.length = config.pelLength // 10
      pel.end_x = pel.x + pel.length
      pel.end_y = pel.y
      pel.connect_previous_x = pel.x
      pel.connect_previous_y = pel.y
      pel.connect_next_x = pel.x
      pel.connect_next_y = pel.y
      pel.title_x = pel.x + config.textSpace // 10
      pel.title_y = pel.y - config.textHeight
      pel.data_x = pel.x + config.textSpace
      pel.data_y = pel.y + config.textSpace
      pel.data_width = pel.length - 2 * config.textSpace
      pel.data_height = config.textHeight // 15
      resultData[heightVoltageBus.uid] = { ...heightVoltageBus, ...pel }
      positionX = pel.end_x
      positionY = pel.end_y
      for (const box of heightVoltageBus.children || []) {
        // 整体柜子图形,不在一次系统图上显示
        for (const cabinet of box.children || []) {
          this.idMapUid[cabinet.id] = cabinet.uid
          // 各馈线柜图形,一条横向直线
          const pel = {}
          pel.pointType = cabinet.infobject && cabinet.infobject.pointType ? parseInt(cabinet.infobject.pointType) : 0
          pel.x = positionX
          pel.y = positionY
          pel.width = 0
          pel.length = config.pelLength
          pel.end_x = pel.x + pel.length
          pel.end_y = pel.y
          pel.connect_previous_x = pel.x
          pel.connect_previous_y = pel.y
          pel.connect_next_x = pel.x
          pel.connect_next_y = pel.y
          pel.title_x = pel.x + config.textSpace
          pel.title_y = pel.y - config.textHeight
          pel.data_x = pel.x + config.textSpace
          pel.data_y = pel.y + config.textSpace
          pel.data_width = pel.length - 2 * config.textSpace
          pel.data_height = config.textHeight
          resultData[cabinet.uid] = { ...cabinet, ...pel }
          positionX = pel.end_x + config.pelSpaceX
          positionY = pel.end_y
          pointPositionX = pel.connect_next_x
          pointPositionY = pel.connect_next_y
          for (const point of cabinet.children) {
            this.idMapUid[point.id] = point.uid
            // 监测点图形,各种形状
            const pel = {}
            pel.pointType = point.infobject && cabinet.infobject.pointType ? parseInt(point.infobject.pointType) : 0
            pel.x = pointPositionX
            pel.connect_previous_x = pel.x
            if (pel.pointType === 4) {
              // 变压器
              pointPositionY += config.pelSpaceY
              pel.y = pointPositionY
              pel.width = config.pelLength
              pel.length = config.pelWidth
              pel.end_x = pel.x
              pel.end_y = pel.y + pel.width * 8 / 5
              pel.connect_previous_y = pel.y
              pel.connect_next_x = pel.end_x
              pel.connect_next_y = pel.end_y
              pel.title_x = pel.x + config.textSpace
              pel.title_y = pel.y - config.textHeight - pel.length
              pel.data_x = pel.x + config.textSpace
              pel.data_y = pel.y + config.textSpace
            } else if (pel.pointType % 2 === 1) {
              // 横向图元
              pointPositionY += config.pelSpaceY
              pel.y = pointPositionY
              pel.width = config.pelLength
              pel.length = config.pelWidth
              pel.end_x = pel.x + pel.width
              pel.end_y = pel.y
              pel.connect_previous_y = pel.y
              pel.connect_next_x = pel.x
              pel.connect_next_y = pel.y
              pel.title_x = pel.x + config.textSpace
              pel.title_y = pel.y - config.textHeight - pel.length
              pel.data_x = pel.x + config.textSpace
              pel.data_y = pel.y + config.textSpace
              pel.focus_x = pel.x + pel.length * 5 / 2
              pel.focus_y = pel.y - pel.length / 2
              pel.focus_end_x = pel.focus_x + pel.width - pel.length * 5
              pel.focus_end_y = pel.focus_y + pel.length
            } else {
              // 纵向图元
              pointPositionY += config.pelSpaceY
              pel.y = pointPositionY
              pel.width = config.pelWidth
              pel.length = config.pelLength
              pel.end_x = pel.x
              pel.end_y = pel.y + pel.length
              pel.connect_previous_y = pel.y
              pel.connect_next_x = pel.end_x
              pel.connect_next_y = pel.end_y
              pel.title_x = pel.x + config.textSpace + pel.width / 2
              pel.title_y = pel.y
              pel.data_x = pel.title_x
              pel.data_y = pel.title_y + config.textSpace
              pel.focus_x = pel.x - pel.width / 2
              pel.focus_y = pel.y + pel.width * 5 / 2
              pel.focus_end_x = pel.focus_x + pel.width
              pel.focus_end_y = pel.focus_y + pel.length - pel.width * 5
              pointPositionY = pel.y + config.pelSpaceY + pel.length
            }
            pel.data_width = config.pelLength - 2 * config.textSpace
            pel.data_height = config.pelLength
            pel.connect = point.previous_uid
            resultData[point.uid] = { ...point, ...pel }
          }
        }
      }
    }
    return resultData
  }
}
